﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Moq;
using NUnit.Framework;

namespace PoolKit.Tests
{
    [TestFixture]
    public class PoolToleratingStarvationTests
    {
        private class StringCreationStrategy : ResourceCreationStrategy<string>
        {
            private int _objectsProduced;

            public override string Create()
            {
                _objectsProduced++;
                return Guid.NewGuid().ToString("N");
            }

            public override ThreadingContract ThreadingContract
            {
                get
                {
                    return ThreadingContract.Singlethreaded;
                }
            }

            public int ObjectsProduced
            {
                get { return _objectsProduced; }
            }
        }

        private class ByteArrayCreationStrategy : ResourceCreationStrategy<byte[]>
        {
            public override byte[] Create()
            {
                return new byte[8];
            }
        }

        [Test]
        public void MakeSurePoolToleratesStarvationByProducingExtraResourcesWhenNeeded()
        {
            var pool = GenericPoolCollaboratorsFactory.CreateDefaultGenericPool(new[] {"one", "two", "three"});

            var resourcesBorrowed = new List<string>();

            var resourceCreationStrategy = new StringCreationStrategy();

            using (var alwaysReadyPool = new PoolToleratingStarvation<string>(pool, resourceCreationStrategy))
            {
                for (int i = 0; i < 5; i++)
                {
                    resourcesBorrowed.Add(alwaysReadyPool.Lend());
                }
            }

            Assert.IsTrue(resourcesBorrowed.Contains("one"));
            Assert.IsTrue(resourcesBorrowed.Contains("two"));
            Assert.IsTrue(resourcesBorrowed.Contains("three"));

            Assert.AreEqual(2, resourceCreationStrategy.ObjectsProduced);
        }

        [Test]
        public void MakeSureReturningObjectsThatWereReallyPooledAtSomePointPutsThemToPool()
        {
            var coreResources = new[] {"one", "two", "three"}.ToList();

            var pool = GenericPoolCollaboratorsFactory.CreateDefaultGenericPool(coreResources);

            var resourcesBorrowed = new List<string>();

            var resourceCreationStrategy = new StringCreationStrategy();

            using (var alwaysReadyPool = new PoolToleratingStarvation<string>(pool, resourceCreationStrategy))
            {
                for (int i = 0; i < 5; i++)
                {
                    alwaysReadyPool.Lend();
                }

                alwaysReadyPool.AcceptBack("one");
                alwaysReadyPool.AcceptBack("two");
                alwaysReadyPool.AcceptBack("three");

                do
                {
                    string resource = alwaysReadyPool.Lend();

                    if (coreResources.Contains(resource))
                    {
                        resourcesBorrowed.Add(resource);
                    }

                    Thread.Sleep(150);
                }
                while (resourcesBorrowed.Count != coreResources.Count);
            }

            CollectionAssert.AreEquivalent(coreResources, resourcesBorrowed);
        }

        [Test]
        public void MakeSureReturningObjectsThatWereNeverInThePoolButWereConstructedByUsDoesNotPoolThem()
        {
            var coreResources = new[] { "one", "two", "three" }.ToList();

            var pool = GenericPoolCollaboratorsFactory.CreateDefaultGenericPool(coreResources);

            var resourcesBorrowed = new List<string>();

            var resourceCreationStrategy = new StringCreationStrategy();

            using (var alwaysReadyPool = new PoolToleratingStarvation<string>(pool, resourceCreationStrategy))
            {
                var resources = new List<string>();

                for (int i = 0; i < 5; i++)
                {
                    resources.Add(alwaysReadyPool.Lend());
                }

                foreach (string resource in resources)
                {
                    alwaysReadyPool.AcceptBack(resource);
                }

                do
                {
                    string resource;

                    // Note: going directly to the underlying pool
                    if (pool.TryLend(out resource))
                    {
                        if (coreResources.Contains(resource))
                        {
                            resourcesBorrowed.Add(resource);
                        }
                    }

                    Thread.Sleep(150);
                }
                while (resourcesBorrowed.Count != coreResources.Count);
            }

            CollectionAssert.AreEquivalent(coreResources, resourcesBorrowed);
        }

        [Test]
        public void MakeSureEvenWithAWrapperYouCannotReturnThingsToPoolMoreThanOnce()
        {
            var coreResources = new[] { "one", "two", "three" }.ToList();

            var pool = GenericPoolCollaboratorsFactory.CreateDefaultGenericPool(coreResources);

            var resourceCreationStrategy = new StringCreationStrategy();

            using (var alwaysReadyPool = new PoolToleratingStarvation<string>(pool, resourceCreationStrategy))
            {
                var resources = new List<string>();

                for (int i = 0; i < 5; i++)
                {
                    resources.Add(alwaysReadyPool.Lend());
                }

                foreach (string resource in resources)
                {
                    alwaysReadyPool.AcceptBack(resource);
                }

                foreach (string resource in resources)
                {
                    Assert.Throws<CannotAcceptResourceException>(
                        delegate()
                            {
                                alwaysReadyPool.AcceptBack(resource);
                            });
                }
            }
        }

        [Test]
        public void MakeSureDisposingOfThePoolToleratingStarvationAlsoDisposesOfTheUnderlyingPool()
        {
            var pool = new Mock<Pool<string>>();
            var resourceCreationStrategy = new Mock<ResourceCreationStrategy<string>>();

            var alwaysReadyPool = new PoolToleratingStarvation<string>(pool.Object, resourceCreationStrategy.Object);
            alwaysReadyPool.Dispose();

            pool.Verify(p=>p.Dispose(), Times.Once());
        }

        [Test]
        [Timeout(10000)]
        public void MakeSureDisposingThePoolMakesItReleaseItsResources()
        {
            var arrayWeakReferences = new List<WeakReference>();
            var arrays = new List<byte[]>();

            using (var pool =
                new PoolToleratingStarvation<byte[]>(
                    GenericPoolCollaboratorsFactory.CreateDefaultGenericPool(new[]{new byte[8], new byte[8]}), 
                    new ByteArrayCreationStrategy()))
            {
                for (int i = 0; i < 5; i++)
                {
                    byte[] array = pool.Lend();

                    arrayWeakReferences.Add(new WeakReference(array));
                    arrays.Add(array);
                }

                foreach (byte[] array in arrays)
                {
                    pool.AcceptBack(array);
                }
            }

            arrays.Clear();

            while (!arrayWeakReferences.All(x => !x.IsAlive))
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();

                Thread.Sleep(200);
            }
        }

        [Test]
        [Timeout(10000)]
        public void MakeSureReturningCreatedOnTheFlyResourcesMakesThePoolForgetAboutThem()
        {
            var arrayWeakReferences = new List<WeakReference>();
            var arrays = new List<byte[]>();

            using (var pool =
                new PoolToleratingStarvation<byte[]>(
                    GenericPoolCollaboratorsFactory.CreateDefaultGenericPool(new[] { new byte[8], new byte[8] }),
                    new ByteArrayCreationStrategy()))
            {
                // draining the underlying static pool
                pool.Lend();
                pool.Lend();

                // now - only newly created resources
                for (int i = 0; i < 3; i++)
                {
                    byte[] array = pool.Lend();

                    arrayWeakReferences.Add(new WeakReference(array));
                    arrays.Add(array);
                }

                foreach (byte[] array in arrays)
                {
                    pool.AcceptBack(array);
                }

                arrays.Clear();

                while (!arrayWeakReferences.All(x => !x.IsAlive))
                {
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                    GC.Collect();

                    Thread.Sleep(200);
                }
            }
        }
    }
}